<?php

namespace SinoPHP\BbdBoss\Library;

use InvalidArgumentException;
use Upfor\UpforPHP\Library\Curl\Curl;
use Upfor\UpforPHP\Library\Curl\MultiCurl;
use SinoPHP\BbdBoss\Defines\DnspodCode;

/**
 * DNSPOD API
 */
class Dnspod {

    /**
     * DNSPOD配置信息
     *
     * @var null | array
     */
    private $config = null;

    /**
     * 托管在DNSPOD域名ID
     * 
     * @var null | int
     */
    private $domainId = null;

    /**
     * 公共参数
     * 
     * @var null | array
     */
    private $commonData = null;

    /**
     * 监控回调地址
     * 
     * @var null|string
     */
    private $callbackUrl = null;

    /**
     * 默认ttl
     */
    const DEFAULT_TTL = 300;

    /**
     * 请求接口命名格式化镜像
     */
    private static $apiActionImage = array(
        'info_version' => 'Info.Version',
        'domain_list'  => 'Domain.List',
        'record_list'  => 'Record.List',
        'record_type'  => 'Record.Type',
        'record_line'  => 'Record.Line',
        'add_record'   => 'Record.Create',
        'edit_record'  => 'Record.Modify',
        'record_status'=> 'Record.Status',
        'remove_record'=> 'Record.Remove',
        'info_record'  => 'Record.Info',
        'add_monitor'  => 'Monitor.Create',
        'edit_monitor' => 'Monitor.Modify',
        'del_monitor'  => 'Monitor.Remove',
        'info_monitor' => 'Monitor.Info',
        'monitor_list' => 'Monitor.List',
        'batch_record_modify' => 'Batch.Record.Modify',
        'batch_record_create' => 'Batch.Record.Create',
        'Batch_detail' => 'Batch.Detail',
        'user_log'     => 'User.Log',
        'domain_log'   => 'Domain.Log',
        'info_monitor' => 'Monitor.Info',
    );

    /**
     * 构造函数
     * 
     * @param string $domainIdLabel 域名选择标识：default, inner, outer
     */
    public function __construct($domainIdLabel = 'default', $environment = null) {
        $this->config = config('api', 'dnspod', $environment);
        $this->setDomainId($domainIdLabel);
        $this->setCallbackUrl($domainIdLabel);
        $this->setCommonData();
    }

    /**
     * 获取购买域名列表
     * 
     * @return array
     */
    public function getDomainList(){
        $requestData = [];
        return $this->getCurlResponse('domain_list', $requestData);
    }

    /**
     * 获取域名操作日志
     * 
     * @param  array  $params
     * @return mixed
     */
    public function getRecordLogList($params = array()) {
        if (empty($params)) {
            return false;
        }

        $data = array('domain_id' => $this->domainId);
        isset($params['offset']) && $data['offset'] = $params['offset'];
        isset($params['length']) && $data['length'] = $params['length'];

        return $this->getCurlResponse('domain_log', $data);
    }

    /**
     * 获取域名记录列表
     * 
     * @param  array $params
     * @return array
     */
    public function getRecordList($params) {
        // 查询偏移起始值
        $offset = empty($params['offset']) || !is_numeric($params['offset']) ? 0 : $params['offset'];

        $requestData = [
            'offset' => $offset,
            'domain_id' => $this->domainId
        ];
        if (!empty($params['length'])) {
            $requestData['length'] = (int) $params['length'];
        }

        if (!empty($params['sub_domain'])) {
            $requestData['sub_domain'] = $params['sub_domain'];
        }

        if (!empty($params['keyword'])) {
            $requestData['keyword'] = $params['keyword'];
        }

        return $this->getCurlResponse('record_list', $requestData);
    }

    /**
     * 添加解析记录
     * 
     * @param  array  $data
     * @return array
     */
    public function addRecord(array $data) {
        if (empty($data)) {
            return false;
        }

        $ttl = empty($data['ttl']) || !preg_match('/^[1-9][0-9]*$/', $data['ttl']) ? self::DEFAULT_TTL : $data['ttl'];
        $requestData = array(
            'domain_id'   => $this->domainId,
            'sub_domain'  => $data['subDomain'],
            'record_type' => $data['recordType'],
            'record_line' => $data['recordLine'],
            'value'       => $data['record'],
            'mx'          => '',
            'ttl'         => $ttl,
        );
        // DNSPOD 会优先取用record_line_id
        if (!empty($data['recordLineId'])) {
            $requestData['record_line_id'] = $data['recordLineId'];
        }

        return $this->getCurlResponse('add_record', $requestData);
    }

    /**
     * 修改解析记录
     * 
     * @param  array  $data
     * @return array      
     */
    public function editRecord(array $data) {
        if (empty($data) || empty($data['recordId'])) {
            return false;
        }

        $ttl = empty($data['ttl']) || !preg_match('/^[1-9][0-9]*$/', $data['ttl']) ? self::DEFAULT_TTL : $data['ttl'];
        $requestData = array(
            'domain_id'   => $this->domainId,
            'record_id'   => $data['recordId'],
            'sub_domain'  => $data['subDomain'],
            'record_type' => $data['recordType'],
            'record_line' => $data['recordLine'],
            'value'       => $data['record'],
            'mx'          => '',
            'ttl'         => $ttl,
        );
        // DNSPOD 会优先取用record_line_id
        if (!empty($data['recordLineId'])) {
            $requestData['record_line_id'] = $data['recordLineId'];
        }

        return $this->getCurlResponse('edit_record', $requestData);
    }

    /**
     * 修改记录状态
     * 
     * @param  array  $data
     * @return array
     */
    public function editRecordStatus(array $data) {
        if (empty($data) || empty($data['recordId']) || @!in_array($data['status'], ['enable', 'disable'])) {
            return false;
        }

        $requestData = [
            'domain_id' => $this->domainId,
            'record_id' => $data['recordId'],
            'status' => $data['status'],
        ];

        return $this->getCurlResponse('record_status', $requestData);
    }

    /**
     * 记录批量添加的数据组装
     * 
     * @param  array  $data
     * @param  string $type {multi:并发，batch:批量}
     * @return array
     */
    private function recordBatchAddData($data, $type = "multi") {
        // 组合记录添加数据
        $requestData = array();
        foreach ($data as $key => $param) {
            $ttl = empty($param['ttl']) || !preg_match('/^[1-9][0-9]*$/', $param['ttl']) ? self::DEFAULT_TTL : $param['ttl'];
            $requestData[$key] = array(
                'sub_domain'  => $param['subDomain'],
                'record_type' => $param['recordType'],
                'record_line' => $param['recordLine'],
                'value'       => $param['record'],
                'mx'          => '',
                'ttl'         => $ttl,
            );
            // DNSPOD 会优先取用record_line_id
            if (!empty($param['recordLineId'])) {
                $requestData[$key]['record_line_id'] = $param['recordLineId'];
            }
            // 并发需要主域名ID
            if ('multi' == $type) {
                 $requestData[$key]['domain_id'] = $this->domainId;
            }
        }

        return $requestData;
    }

    /**
     * 并发添加记录
     * 
     * @param   array $data
     * @return  array
     */
    public function addRecordMulti(array $data) {
        if (empty($data)) {
            return false;
        }

        // 组合记录添加数据
        $requestData = $this->recordBatchAddData($data);

        return $this->getCurlMultiResponse('add_record', $requestData);
    }

    /**
     * 批量添加记录
     * 
     * 批量任务放到DNSPOD队列中
     * 根据任务ID异步查询任务状态
     * @param  array  $data
     * @return array
     */
    public function batchAddRecord(array $data) {
        if (empty($data)) {
            return false;
        }

        // 组合记录添加数据
        $recordData = $this->recordBatchAddData($data, 'batch');

        $requestData = [
            'domain_id' => $this->domainId,
            'records' => json_encode(@array_values($recordData))
        ];

        return $this->getCurlResponse('batch_record_create', $requestData);
    }

    /**
     * 获取批量的任务详情
     * 
     * @param  array  $data
     * @return array
     */
    public function getBatchDetail(array $data) {
        if (empty($data['jobId'])) {
            return false;
        }

        $requestData = [
            'job_id' => $data['jobId']
        ];

        return $this->getCurlResponse('Batch_detail', $requestData);
    }

    /**
     * 批量修改记录
     * 
     * @param   array $data
     * @return  array
     */
    public function editRecordMulti(array $data) {
        if (empty($data)) {
            return false;
        }

        // 组合记录添加数据
        $requestData = array();
        foreach ($data as $key => $param) {
            $ttl = empty($param['ttl']) || !preg_match('/^[1-9][0-9]*$/', $param['ttl']) ? self::DEFAULT_TTL : $param['ttl'];
            $requestData[$key] = [
                'domain_id'   => $this->domainId,
                'record_id'   => $param['recordId'],
                'sub_domain'  => $param['subDomain'],
                'record_type' => $param['recordType'],
                'record_line' => $param['recordLine'],
                'value'       => $param['record'],
                'mx'          => '',
                'ttl'         => $ttl,
            ];
            // DNSPOD 会优先取用record_line_id
            if (!empty($param['recordLineId'])) {
                $requestData[$key]['record_line_id'] = $param['recordLineId'];
            }
        }

        return $this->getCurlMultiResponse('edit_record', $requestData);
    }

    /**
     * 获取记录信息
     * 
     * @param  array  $data
     * @return array
     */
    public function getRecord(array $data) {
        if (empty($data['recordId'])) {
            return false;
        }

        $requestData = array(
            'record_id' => $data['recordId'],
            'domain_id' => $this->domainId,  
        );

        return $this->getCurlResponse('info_record', $requestData);
    }

    /**
     * 删除解析记录
     * 
     * @param  array  $data
     * @return array
     */
    public function deleteRecord(array $data) {
        if (empty($data['recordId'])) {
            return false;
        }

        $requestData = array(
            'record_id' => $data['recordId'],
            'domain_id' => $this->domainId,  
        );

        return $this->getCurlResponse('remove_record', $requestData);
    }

    /**
     * 批量删除记录
     * 
     * @param  array  $data
     * @return array
     */
    public function deleteRecordMulti(array $data) {
        if (empty($data)) {
            return false;
        }

        $requestData = array();
        foreach ($data as $key => $param) {
            $requestData[$key] = [
                'record_id' => $param['recordId'],
                'domain_id' => $this->domainId,
            ];
        }

        return $this->getCurlMultiResponse('remove_record', $requestData);
    }

    /**
     * 添加监控
     * 
     * @param  array $data
     * @return array
     */
    public function addMonitor(array $data){
        if (empty($data)) {
            return false;
        }

        $monitorCommonData = $this->getMonitorCommonData();

        $requestData = array(
            'record_id'    => $data['recordId'],
            'monitor_path' => $this->config['monitorPath'] . $data['recordHost'],
            'bak_ip'       => $data['bakIp'],
        );

        $requestData = @array_merge($requestData, $monitorCommonData);

        // 回调URL
        if (@!empty(trim($data['callbackUrl']))) {
            $requestData['callback_url'] = $data['callbackUrl'];
        }
        // 监控节点
        if (@!empty(trim($data['points']))) {
            $requestData['points'] = $data['points'];
        }

        return $this->getCurlResponse('add_monitor', $requestData);
    }

    /**
     * 批量添加监控
     * 
     * @param  array $data
     * @return mixed 
     */
    public function addMonitorMulti(array $data) {
        if (empty($data)) {
            return false;
        }

        $monitorCommonData = $this->getMonitorCommonData();
        $requestData = array();
        $tempData = array();
        foreach ($data as $key => $param) {
            $tempData = array(
                'record_id'    => $param['recordId'],
                'monitor_path' => $this->config['monitorPath'] . $param['recordHost'],
                'bak_ip'       => $param['bakIp'],
            );

            $requestData[$key] = @array_merge($monitorCommonData, $tempData);

            // 回调URL
            if (@!empty(trim($param['callbackUrl']))) {
                $requestData[$key]['callback_url'] = $param['callbackUrl'];
            }
            // 监控节点
            if (@!empty(trim($param['points']))) {
                $requestData[$key]['points'] = $param['points'];
            } 
        }

        return $this->getCurlMultiResponse('add_monitor', $requestData);
    }

    /**
     * 获取监控信息
     * 
     * @param  array  $data
     * @return array
     */
    public function getMonitor(array $data) {
        if (empty($data['monitorId'])) {
            return false;
        }

        $requestData = array(
            'monitor_id' => $data['monitorId'],
            // 'domain_id' => $this->domainId,  
        );

        return $this->getCurlResponse('info_monitor', $requestData);
    }

    /**
     * 获取监控列表
     * 
     * @param  array|null $data
     * @return array
     */
    public function getMonitorList(array $data = []) {
        $requestData = $data;

        return $this->getCurlResponse('monitor_list', $requestData);
    }

    /**
     * 根据记录ID获取监控ID
     * 
     * @param  array  $data
     * @return integer
     */
    public function getMonitorIdByRecord(array $data) {
        if (empty($data['recordId'])) {
            return false;
        }

        // 请求监控列表
        $monitorResponse = $this->getMonitorList();
        if (empty($monitorResponse['status']['code']) || $monitorResponse['status']['code'] != 1 || empty($monitorResponse['monitors'])) {
            return false;
        }

        $monitorList = $monitorResponse['monitors'];

        foreach ($monitorList as $monitor) {
            if ($monitor['record_id'] == $data['recordId']) {
                return $monitor['monitor_id'];
            }
        }

        return 0;
    }

    /**
     * 修改监控
     * 
     * @param  array  $data
     * @return array
     */
    public function editMonitor(array $data) {
        if (empty($data)) {
            return false;
        }

        $monitorCommonData = $this->getMonitorCommonData();

        $requestData = array(
            'monitor_id' => $data['monitorId'],
            'monitor_path' => $this->config['monitorPath'] . $data['recordHost'],
            'bak_ip'       => $data['bakIp'],
        );

        $requestData = @array_merge($requestData, $monitorCommonData);

        // 回调URL
        if (!empty(trim($data['callbackUrl']))) {
            $requestData['callback_url'] = $data['callbackUrl'];
        }
        // 监控节点
        if (@!empty(trim($data['points']))) {
            $requestData['points'] = $data['points'];
        }

        return $this->getCurlResponse('edit_monitor', $requestData);
    }

    /**
     * 批量修改监控
     * 
     * @param  array  $data
     * @return array
     */
    public function editMonitorMulti(array $data) {
        if (empty($data)) {
            return false;
        }

        $monitorCommonData = $this->getMonitorCommonData();
        $requestData = array();
        $tempData = array();
        foreach ($data as $key => $param) {
            $tempData = array(
                // 'record_id'    => $param['recordId'],
                'monitor_id'   => $param['monitorId'],
                'monitor_path' => $this->config['monitorPath'] . $param['recordHost'],
                'bak_ip'       => $param['bakIp'],
            );

            $requestData[$key] = @array_merge($monitorCommonData, $tempData);

            // 回调URL
            if (@!empty(trim($param['callbackUrl']))) {
                $requestData[$key]['callback_url'] = $param['callbackUrl'];
            }
            // 监控节点
            if (@!empty(trim($param['points']))) {
                $requestData[$key]['points'] = $param['points'];
            } 
        }

        return $this->getCurlMultiResponse('edit_monitor', $requestData);
    }

    /**
     * 删除监控
     * 
     * @param  array  $data
     * @return array
     */
    public function deleteMonitor(array $data) {
        if(empty($data['monitorId'])){
            return false;
        }

        $requestData = array(
            'domain_id'  => $this->domainId,
            'monitor_id' => $data['monitorId'],
        );

        return $this->getCurlResponse('del_monitor', $requestData);
    }

    /**
     * 监控添加的公用参数
     * 
     * @return array
     */
    private function getMonitorCommonData() {
        return [
            'domain_id'        => $this->domainId,
            'port'             => $this->config['port'],
            'monitor_interval' => $this->config['monitorInterval'],
            'host'             => $this->config['monitorHost'],
            'monitor_type'     => $this->config['monitorType'],
            'points'           => $this->config['points'],
            'sms_notice'       => $this->config['smsNotice'],
            'callback_url'     => $this->callbackUrl,
            'callback_key'     => $this->config['callbackKey'],
        ];
    }

    /**
     * 获取DNSPOD响应码对应信息
     * 
     * @param  integer $dnspodCode
     * @param  string  $action common:公共参数；record:记录相关；monitor:监控相关
     * @return string
     */
    public function getDnspodCodeMsg($dnspodCode, $action = 'record') {
        $dnspodCodeStatus = DnspodCode::$dnspodCode;

        $msg = '未知的DNSPOD API 错误';
        if (!empty($dnspodCodeStatus[$action][$dnspodCode])) {
            return $dnspodCodeStatus[$action][$dnspodCode];
        } elseif (!empty($dnspodCodeStatus['common'][$dnspodCode])) {
            return $dnspodCodeStatus['common'][$dnspodCode];
        }

        return $msg;
    }

    /**
     * 获取解析记录线路
     *
     * 分线路为三级结构
     *     分为导航ISP(移动) 》大区(华北) 》具体线路(联通) 三级结构
     * @return array
     */
    public function getDbRecordLines() {
        $dnspodLineObj = model("DomainDnspodLine");

        // 线路导航配置
        $navList = $this->config['recordLine']['navList'];
        // 排除线路
        $exceptLine = $this->config['recordLine']['exceptLine'];

        // 选取的线路
        $cols = [
            'id',
            'line_name',
            'line_id',
            'is_group',
            'group_id'
        ];
        $where = [
            'status' => 1,
        ];
        $allLines = $dnspodLineObj->getList($cols, $where);

        // 获得大区列表
        $regionList = $dnspodLineObj->getList($cols, ['status' => 1, 'is_group' => 1]);
        $regions = array();
        foreach ($regionList as $region) {
            $regions[$region['id']] = $region;
        }

        // 组装线路分级结构
        foreach ($navList as &$nav) {
            foreach ($allLines as $key => $line) {
                if (in_array($nav['line_name'], ['移动', '电信', '联通'])) {
                    if (stripos($line['line_name'], $nav['line_name'])) {
                        // 截取省份
                        $provinceTemp = mb_substr($line['line_name'], 0, 2); 

                        // 组合大区信息
                        $nav['subLines'][$line['group_id']]['line_name'] = $regions[$line['group_id']]['line_name'];
                        $nav['subLines'][$line['group_id']]['subLines'][] = [
                            'id' => $line['id'],
                            'province' => $provinceTemp,
                            'line_name' => $line['line_name']
                        ];
                        unset($allLines[$key]);
                    }
                } elseif("海外其他" == $nav['line_name']) {
                    if (0 === stripos($line['line_id'], '5=')) {
                        $nav['subLines'][] = [
                            'id' => $line['id'],
                            'province' => '海外',
                            'line_name' => $line['line_name']
                        ];
                    }
                } else {
                    if (false === stripos($line['line_id'], '5=') && !in_array($line['line_name'], $exceptLine)) {   
                        $nav['subLines'][] = [
                            'id' => $line['id'],
                            'province' => '国内',
                            'line_name' => $line['line_name']
                        ];
                    }
                }
            }
        }

        return $navList;
    }

    /**
     * DNSPOD托管域名ID
     *
     * @param  string $domainIdLabel
     * @return none
     */
    private function setDomainId($domainIdLabel = 'default') {
        $labelTemp = $domainIdLabel;
        $projectSet = [
            'fuse' => 'fuseDomainId',
            'self' => 'domainId'
        ];

        $project = $projectSet['self'];
        if (is_array($labelTemp)) {
            $domainIdLabel = $labelTemp['label'];
            $project = !empty($labelTemp['project']) ? $projectSet[$labelTemp['project']] : $project;
        }

        if (!isset($this->config['domainId'][$domainIdLabel]['id']) || empty($this->config['domainId'][$domainIdLabel]['id'])) {
            throw new InvalidArgumentException('DNSPOD API domain_id is miss!');
        }
        $this->domainId = $this->config[$project][$domainIdLabel]['id'];
    }

    /**
     * 设置监控回到地址
     */
    private function setCallbackUrl($domainIdLabel) {
        $labelTemp = $domainIdLabel;
        if (is_array($labelTemp)) {
            $domainIdLabel = $labelTemp['label'];
        }

        if (!isset($this->config['callbackUrl'][$domainIdLabel]) || empty($this->config['callbackUrl'][$domainIdLabel])) {
            throw new InvalidArgumentException('DNSPOD API callback Url is miss!');
        }
        $this->callbackUrl = $this->config['callbackUrl'][$domainIdLabel];
    }

    /**
     * 获得公共的请求参数
     * 
     * @return array
     */
    private function setCommonData() {
        $this->commonData = array(
            'login_token' => $this->config['apiToken'],
            'format' => $this->config['apiFormat'],
        );
    }

    /**
     * 获取接口请求地址
     * 
     * @param  string $apiAction
     * @return string
     */
    private function getApiUrl($apiAction = '') {
        $apiActionImage = self::$apiActionImage;

        if (!isset($apiActionImage[$apiAction]) || empty($apiActionImage[$apiAction])) {
            throw new InvalidArgumentException('DNSPOD API action is invlalid!');
        }

        return $this->config['apiHost'] . $apiActionImage[$apiAction];
    }

    /**
     * 获取Curl请求返回值
     * 
     * @param  string $action
     * @param  array  $data
     * @return array
     */
    private function getCurlResponse($action, $data) {
        $result = false;
        $apiUrl = $this->getApiUrl($action);
        // 参数
        $data = @array_merge($data, $this->commonData);

        // 请求接口
        $response = $this->curl($apiUrl, $data);

        return !empty(json_decode($response, true)) ? json_decode($response, true) : $response;
    }

    /**
     * 获得并发curl请求的返回值
     * 
     * @param  string $action
     * @param  array  $data
     * @return array
     */
    private function getCurlMultiResponse($action, array $data) {
        $apiUrl = $this->getApiUrl($action);

        // 并发请求接口
        return $this->curlMulti($apiUrl, $data);;
    }

    /**
     * CURL http请求
     * 
     * @param  string $apiUrl
     * @param  array  $data
     * @param  integer $timeOut
     * @return string
     */
    private function curl($apiUrl, $data, $timeOut = 30) {
        $curlObj = new Curl();

        $curlObj->setUrl($apiUrl);
        $curlObj->setTimeout($timeOut);
        // setOpt 默认选项
        $this->curlDefaultSetOpt($curlObj);
        // $curlObj->setOpt(CURLOPT_POSTFIELDS, http_build_query($data));
        $curlObj->setOpt(CURLOPT_POSTFIELDS, $curlObj->buildPostData($data));

        // 执行curl
        $response = $curlObj->exec();

        return $response;
    }

    /**
     * CURL 并发请求
     * 
     * @param  string  $apiUrl 
     * @param  array   $data  
     * @param  integer $timeOut
     * @return mixed
     */
    private function curlMulti($apiUrl, $data, $timeOut = 60) {
        $multiCurlObj = new MultiCurl();
        $result = ['success' => null, 'fail' => null];

        // $multiCurlObj->setHeader('Content-Type', 'application/json');
        $multiCurlObj->setTimeout($timeOut);
        // 默认curl 选项
        $this->curlDefaultSetOpt($multiCurlObj);

        // 添加句柄
        $curlObj = null;
        $temp = array();
        if (is_array($data)) {
            foreach ($data as $key => &$val) {
                $val = array_merge($val, $this->commonData);
                $curlObj = $multiCurlObj->addPost($apiUrl, $val);
                $temp[$curlObj->id] = $key;
            }
        }

        // 设置成功回调函数
        $multiCurlObj->success(function ($instance) use (&$result, $temp) {
            $response = json_decode($instance->response, true);
            $result['success'][$temp[$instance->id]] = $response;
        });
        // 设置失败回调函数
        $multiCurlObj->error(function($instance) use (&$result, $temp) {
            $result['fail'][$temp[$instance->id]] = $instance->response ? $instance->response : false;
        });

         // 开始请求接口
        $multiCurlObj->start();
        
        return $result;
    }

    /**
     * CURL setOpt 默认选项部分
     * 
     * @param  object &$curlObj
     * @return none           
     */
    private function curlDefaultSetOpt(&$curlObj){
        // $curlObj->setOpt(CURLOPT_RETURNTRANSFER, 1);
        $curlObj->setOpt(CURLOPT_POST, 1);
        $curlObj->setOpt(CURLOPT_HEADER, 0);
        $curlObj->setOpt(CURLOPT_SSL_VERIFYPEER, 0);
        $curlObj->setOpt(CURLOPT_SSL_VERIFYHOST, 0);
        $curlObj->setOpt(CURLOPT_SSLVERSION, 1);
        $curlObj->setOpt(CURLOPT_USERAGENT, $this->config['userAgent']);
        // $curlObj->setOpt(CURLOPT_COOKIE, $cookie);
    }

}
